package org.wattdepot.cli;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.client.WattDepotClientException;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Processes the power chart command to produce an html file.
 * 
 * @author Dean Kim
 * 
 */
public class ChartPowerCommandCli {

  private static final String CHART_POWER_COMMAND_ERROR = "chart power error. Usage: chart "
      + "power [generated|consumed] {source} {startday} {endday} sampling-interval {minutes} "
      + "file {filename}";
  protected static final String CHART_POWER_NET_ERROR = "There is a network error while "
      + "processing the chart power command.";

  private static final String HTML_HEAD = "<!--copyright (c) 2009 Google inc."
      + "You are free to copy and use this sample. License can be found here: http:"
      + "//code.google.com/apis/ajaxsearch/faq/#license-->\n<!DOCTYPE html PUBLIC \"-//W3C//DTD "
      + "XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n "
      + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta http-equiv=\"content-type "
      + "content=\"text/html; charset=utf-8\"/>\n<title>\nGoogle Visualization API Sample\n"
      + "</title>\n<script type=\"text/javascript\" src=\"http://www.google.com/jsapi\">"
      + "</script>\n<script type=\"text/javascript\">\ngoogle.load(\'visualization\', \'1\'"
      + ", {packages: [\'linechart\']});\n</script>\n<script type=\"text/javascript\">"
      + "\nfunction drawVisualization() {\nvar data = new google.visualization.DataTable();\n";

  private static final String HTML_TAIL = "new google.visualization.LineChart(document."
      + "getElementById(\'visualization\')).\ndraw(data, null);\n}\n google.setOnLoadCallback"
      + "(drawVisualization);\n</script>\n</head>\n<body style=\"font-family: Arial;border: "
      + "0 none;\">\n<div id=\"visualization\" style=\"width: 800px; height: 600px;\"></div>\n"
      + "</body>";

  /**
   * Verifies required arguments.
   * 
   * @param input Tokenized string command.
   * @return True if arguments are valid and false otherwise.
   */
  protected boolean verifyArguments(String[] input) {

    boolean valid = false;

    if ((input[1].equals("POWER"))
        && ((input[2].equals("GENERATED")) || (input[2].equals("CONSUMED")))
        && (input[6].equals("SAMPLING-INTERVAL"))) {
      valid = true;
    }

    return valid;
  }

  /**
   * Verifies values of the command line arugments.
   * 
   * @param startDate Date (YYYY-MM-DD) passed as a string.
   * @param endDate Date (YYYY-MM-DD) passed as a string.
   * @param samplingArg sampling-interval argument.
   * @param minutes Minutes passed as a string.
   * @param fileArg Filename argument.
   * @return True if value arguments are valid and false if not valid.
   * @throws WattDepotClientException WattDepotClientException thrown.
   */
  protected boolean verifyNonSourceArgumentValues(String startDate, String endDate,
      String samplingArg, String minutes, String fileArg) throws WattDepotClientException {

    boolean valid = false;

    if ((this.verifyDate(startDate)) && (this.verifyDate(endDate))
        && ("SAMPLING-INTERVAL".equals(samplingArg)) && (this.verifyMinutes(minutes))
        && ("FILE".equals(fileArg))) {
      valid = true;

    } // end if (this.verifySource(client, source))

    return valid;
  }

  /**
   * Verifies if Source exists in the WattDepot server.
   * 
   * @param client Valid WattDepot client.
   * @param stringSource Source name.
   * @return True if matching source was found and false if not.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected boolean verifySource(WattDepotClient client, String stringSource)
      throws WattDepotClientException {

    boolean valid;

    try {
      client.getSource(stringSource);
      valid = true;
    }
    catch (ResourceNotFoundException e) {
      valid = false;
    }

    return valid;
  }

  /**
   * Verifies if WattDepotClient is valid.
   * 
   * @param client WattDepotClient to validate.
   * @return True if WattDepotClient passes validation.
   */
  protected boolean verifyNetwork(WattDepotClient client) {

    return (client.isHealthy());

  }

  /**
   * Verified date input.
   * 
   * @param inputDate String input of day.
   * @return True if date is valid and false if the date is invalid.
   */
  protected boolean verifyDate(String inputDate) {

    String[] tokenizedString;
    int year;
    int month;
    int day;
    XMLGregorianCalendar xmlCal;

    boolean valid;

    tokenizedString = inputDate.split("-");

    if (tokenizedString.length == 3) {
      try {
        year = Integer.parseInt(tokenizedString[0]);
        month = Integer.parseInt(tokenizedString[1]);
        day = Integer.parseInt(tokenizedString[2]);
        xmlCal = DatatypeFactory.newInstance().newXMLGregorianCalendar();
        xmlCal.setYear(year);
        xmlCal.setMonth(month);
        xmlCal.setDay(day);
      } // end try
      catch (NumberFormatException e) {
        valid = false;
      }
      catch (IllegalArgumentException e) {
        valid = false;
      }
      catch (DatatypeConfigurationException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      }
      valid = true;
    } // end (tokenizedString.length == 3)
    else {
      valid = false;
    } // end else
    return valid;

  }

  /**
   * Verifies minutes to check if the string is a valid number and the number is greater than 0.
   * 
   * @param stringMinutes Minutes to verify.
   * @return True if minutes is a valid number and greater than 0.
   */
  protected boolean verifyMinutes(String stringMinutes) {

    boolean valid = false;
    int minutes;

    try {
      minutes = Integer.parseInt(stringMinutes);
      if (minutes > 0) {
        valid = true;
      }
    }
    catch (NumberFormatException e) {
      valid = false;
    }

    return valid;
  }

  /**
   * Produces the XMLGregorianCalendar with time set to the beginning of the day provided.
   * 
   * @param date Date to use.
   * @return XMLGregorianCalendar set to the start of the day provided.
   */
  protected XMLGregorianCalendar getStartDateTime(String date) {

    XMLGregorianCalendar xmlCal = null;
    String[] tokenizedDate = date.split("-");
    try {
      xmlCal = DatatypeFactory.newInstance().newXMLGregorianCalendar();
      xmlCal.setYear(Integer.parseInt(tokenizedDate[0]));
      xmlCal.setMonth(Integer.parseInt(tokenizedDate[1]));
      xmlCal.setDay(Integer.parseInt(tokenizedDate[2]));
      xmlCal.setHour(0);
      xmlCal.setMinute(0);
      xmlCal.setSecond(0);
    }
    catch (DatatypeConfigurationException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    return xmlCal;
  }

  /**
   * Produces the XMLGregorianCalendar with time set to the end of the day provided.
   * 
   * @param date Date to use.
   * @return XMLGregorianCalendar set to the end of the day provided.
   */
  protected XMLGregorianCalendar getEndDateTime(String date) {

    XMLGregorianCalendar xmlCal = null;
    String[] tokenizedDate = date.split("-");
    try {
      xmlCal = DatatypeFactory.newInstance().newXMLGregorianCalendar();
      xmlCal.setYear(Integer.parseInt(tokenizedDate[0]));
      xmlCal.setMonth(Integer.parseInt(tokenizedDate[1]));
      xmlCal.setDay(Integer.parseInt(tokenizedDate[2]));
      xmlCal.setHour(23);
      xmlCal.setMinute(59);
      xmlCal.setSecond(59);
    }
    catch (DatatypeConfigurationException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    return xmlCal;
  }

  /**
   * Checks is time value meets minute interval criteria. Ignores seconds value.
   * 
   * @param time Time value.
   * @param minuteInterval Minute interval criteria.
   * @return True if minute interval criteria is met based on minuteInterval value.
   */
  protected boolean isValidInterval(XMLGregorianCalendar time, int minuteInterval) {

    boolean isValid = false;
    int totalMinutes = 0;

    totalMinutes = time.getHour() * 60;
    totalMinutes += time.getMinute();

    isValid = ((totalMinutes % minuteInterval) == 0);

    return isValid;
  }

  /**
   * Retrieves the power data for the source between the start and end date for the given sampling
   * interval.
   * 
   * @param client Valid WattDepotCleint.
   * @param source Valid source.
   * @param startDate Valid start date (YYYY-MM-DD).
   * @param endDate Valid end date (YYYY-MM-DD).
   * @param interval Minutes specifying sampling interval.
   * @return Total amount of power generated by a source between the dates.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected List<String> getPowerGeneratedData(WattDepotClient client, String source,
      String startDate, String endDate, String interval) throws WattDepotClientException {

    List<String> powerGeneratedList = new ArrayList<String>();
    XMLGregorianCalendar dateCursorXml, endDateXml;
    double power;

    dateCursorXml = this.getStartDateTime(startDate);
    endDateXml = this.getEndDateTime(endDate);

    // Based on the example from the WattDepot Wiki
    while (Tstamp.lessThan(dateCursorXml, endDateXml)) {
      power = client.getPowerGenerated(source, dateCursorXml) / 1000000;
      powerGeneratedList.add(String.format("%.1f", power));
      dateCursorXml = Tstamp.incrementMinutes(dateCursorXml, Integer.valueOf(interval));
    }

    return powerGeneratedList;
  }

  /**
   * Retrieves the power data for the source between the start and end date for the given sampling
   * interval.
   * 
   * @param client Valid WattDepotCleint.
   * @param source Valid source.
   * @param startDate Valid start date (YYYY-MM-DD).
   * @param endDate Valid end date (YYYY-MM-DD).
   * @param interval Minutes specifying sampling interval.
   * @return Total amount of power generated by a source between the dates.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected List<String> getPowerConsumedData(WattDepotClient client, String source,
      String startDate, String endDate, String interval) throws WattDepotClientException {

    List<String> powerGeneratedList = new ArrayList<String>();
    XMLGregorianCalendar dateCursorXml, endDateXml;
    double power;

    dateCursorXml = this.getStartDateTime(startDate);
    endDateXml = this.getEndDateTime(endDate);

    // Based on the example from the WattDepot Wiki
    while (Tstamp.lessThan(dateCursorXml, endDateXml)) {
      power = client.getPowerConsumed(source, dateCursorXml) / 1000000;
      powerGeneratedList.add(String.format("%.1f", power));
      dateCursorXml = Tstamp.incrementMinutes(dateCursorXml, Integer.valueOf(interval));
    }

    return powerGeneratedList;
  }

  /**
   * Generates HTML file with a chart based on the list data.
   * 
   * @param chartData List of data points.
   * @param powerType Type of power (consumed or generated).
   * @param fileName filename of file to create.
   * @return File path.
   * @throws IOException Throws IOException.
   */
  protected String generateHtmlOutput(List<String> chartData, String powerType, String fileName)
      throws IOException {

    int i = 0;
    try {

      FileWriter fileW = new FileWriter(fileName.toLowerCase(Locale.US));
      fileW.write(ChartPowerCommandCli.HTML_HEAD);
      fileW.write("data.addColumn(\'number\', \'" + powerType + "_value\');\n");
      fileW.write("data.addRows(" + chartData.size() + ");\n");
      for (String l : chartData) {
        fileW.write("data.setCell(" + i++ + ",0," + l + ")\n");
      }
      fileW.write(ChartPowerCommandCli.HTML_TAIL);

      fileW.close();

    }
    catch (IOException e) {
      throw e;
    }
    return ("wrote: " + fileName.toLowerCase(Locale.US));

  }

  /**
   * Entry and return point for processing the chart power command.
   * 
   * @param client Valid WattDepot client.
   * @param input Tokenized command.
   * @return Formatted string return for the command.
   * @throws WattDepotClientException Throws WattDepotClientException.
   * @throws IOException Throws IOException.
   */
  protected String processCommand(WattDepotClient client, String[] input)
      throws WattDepotClientException, IOException {

    String commandOutput = CHART_POWER_COMMAND_ERROR;
    List<String> power;

    if (this.verifyNetwork(client)) {

      if ((input.length == 10) && this.verifyArguments(input)
          && (this.verifyNonSourceArgumentValues(input[4], input[5], input[6], input[7],
              input[8]))) {
        if (this.verifySource(client, input[3])) {
          if (input[2].equals("GENERATED")) {
            power = this.getPowerGeneratedData(client, input[3], input[4], input[5], input[7]);
            if (power.isEmpty()) {
              commandOutput = "No power generated values returned for source";
            }
            else {
              commandOutput = this.generateHtmlOutput(power, "Power_Generated", input[9]);
            }
          }
          if (input[2].equals("CONSUMED")) {
            power = this.getPowerConsumedData(client, input[3], input[4], input[5], input[7]);
            if (power.isEmpty()) {
              commandOutput = this.generateHtmlOutput(power, "Consumed", input[9]);
            }
            else {
              commandOutput = "No power consumed values returned for source";
            }
          } // end (this.verifyArguments(input) && ...
        }
        else {
          commandOutput = "Invalid source.";
        }
      }

    }
    else {
      commandOutput = CHART_POWER_NET_ERROR;
    }

    return commandOutput;
  }
}
